#pragma once

#include<functional>
#include <vector>
#include <array>
#include <cstring>
#include <iostream>
#include <fstream> 
#include "tgdefines.h"
#include "tetgen/tetgen.h"

inline void tetgenioOutput(
    std::ostream& mIO,
    const tetgenio& meshData,
    const std::vector<int>* tetFlag)
{
    int numeroftetrahedra_fix = meshData.numberoftetrahedra;
    if (tetFlag) {
        numeroftetrahedra_fix = 0;
        for (int i = 0; i < meshData.numberoftetrahedra; i++) {
            if (tetFlag->at(i) < 0)
                continue;
            numeroftetrahedra_fix++;
        }
    }
    mIO.write((char*)(&numeroftetrahedra_fix), sizeof(int));
    mIO.write((char*)(&meshData.numberoffacets), sizeof(int));
    mIO.write((char*)(&meshData.numberofpoints), sizeof(int));

    if (meshData.numberofpoints > 0) {
        mIO.write((char*)(meshData.pointlist), sizeof(REAL) * meshData.numberofpoints * 3);
    }

    if (meshData.numberoffacets > 0)
    {
        mIO.write((char*)(meshData.facetmarkerlist), sizeof(int) * meshData.numberoffacets);
        for (int i = 0; i < meshData.numberoffacets; i++)
        {
            tetgenio::facet& f = meshData.facetlist[i];
            mIO.write((char*)(&f.numberofholes), sizeof(int));
            mIO.write((char*)(&f.numberofpolygons), sizeof(int));
            if (f.numberofholes > 0) {
                mIO.write((char*)(f.holelist), sizeof(REAL) * f.numberofholes * 3);
            }

            if (f.numberofpolygons > 0)
            {
                for (int j = 0; j < f.numberofpolygons; j++) {
                    tetgenio::polygon& ply = f.polygonlist[j];
                    mIO.write((char*)(&(ply.numberofvertices)), sizeof(int));
                    mIO.write((char*)(ply.vertexlist), sizeof(int) * ply.numberofvertices);
                }
            }
        }
    }

    if (numeroftetrahedra_fix > 0)
    {
        if (tetFlag) {
            for (int i = 0; i < meshData.numberoftetrahedra; i++) {
                if (tetFlag->at(i) < 0)
                    continue;
                int pts[4] = { meshData.tetrahedronlist[i * 4],meshData.tetrahedronlist[i * 4 + 1],meshData.tetrahedronlist[i * 4 + 2],meshData.tetrahedronlist[i * 4 + 3] };
                mIO.write((char*)(pts), sizeof(int) * 4);
            }
            int numNeighbour = meshData.neighborlist ? meshData.numberoftetrahedra : 0;
            int numNeighbour_fix = meshData.neighborlist ? numeroftetrahedra_fix : 0;
            mIO.write((char*)(&numNeighbour_fix), sizeof(int));
            if (numNeighbour_fix > 0) {
                for (int i = 0; i < numNeighbour; i++) {
                    if (tetFlag->at(i) < 0)
                        continue;
                    int pts[4];
                    for (int j = 0; j < 4; j++) {
                        if (meshData.neighborlist[i * 4 + j] >= 0 && tetFlag->at(meshData.neighborlist[i * 4 + j]) > 0)
                            pts[j] = meshData.neighborlist[i * 4 + j];
                        else
                            pts[j] = -1;
                    }
                    mIO.write((char*)(pts), sizeof(int) * 4);
                }
            }
        }
        else
        {
            size_t sz = sizeof(int) * 4 * meshData.numberoftetrahedra;
            mIO.write((char*)(meshData.tetrahedronlist), sz);

            mIO.write((char*)(&numeroftetrahedra_fix), sizeof(int));
            mIO.write((char*)(meshData.neighborlist), sz);
        }
    }
}

inline bool tetgenio2string(const tetgenio& meshData, std::string& strData, const std::vector<int>* tetFlag)
{
    std::stringstream mIO;

    tetgenioOutput(mIO, meshData, tetFlag);

    return Base64::Encode(mIO.str(), &strData);
}

inline bool tetgenio2string(const tetgenio& meshData, std::string& strData)
{
    tetgenio2string(meshData, strData, nullptr);
}

inline bool tetgenio2file(tetgenio& meshData, const std::string& fn, const std::vector<int>* tetFlag)
{
    std::ofstream mIO(fn.c_str(), std::ios_base::out | std::ios_base::binary);
    tetgenioOutput(mIO, meshData, tetFlag);
    mIO.close();
    return true;
}


inline bool tetgeniobystring(tetgenio& meshData,const std::string& strData)
{
    std::string  strOut;

    if (!Base64::Decode(strData, &strOut))
    {
        return false;
    }

    meshData.initialize();

    std::stringstream mIO;
    mIO.write(strOut.data(), strOut.size());

    mIO.read((char*)(&meshData.numberoftetrahedra), sizeof(int));
    mIO.read((char*)(&meshData.numberoffacets), sizeof(int));
    mIO.read((char*)(&meshData.numberofpoints), sizeof(int));

    if (meshData.numberofpoints > 0) {

        meshData.pointlist = new REAL[meshData.numberofpoints * 3];
        mIO.read((char*)(meshData.pointlist), sizeof(REAL)*meshData.numberofpoints * 3);
    }

    if (meshData.numberoffacets > 0)
    {
        meshData.facetlist = new tetgenio::facet[meshData.numberoffacets];
        meshData.facetmarkerlist = new int[meshData.numberoffacets];


        mIO.read((char*)(meshData.facetmarkerlist), sizeof(int)*meshData.numberoffacets);
        for (int i = 0; i < meshData.numberoffacets; i++)
        {
            tetgenio::facet& f = meshData.facetlist[i];
            tetgenio::init(&f);

            mIO.read((char*)(&f.numberofholes), sizeof(int));
            mIO.read((char*)(&f.numberofpolygons), sizeof(int));
            if (f.numberofholes > 0) {
                f.holelist = new REAL[f.numberofholes * 3];
                mIO.read((char*)(f.holelist), sizeof(REAL)*f.numberofholes * 3);
            }

            f.polygonlist = new tetgenio::polygon[f.numberofpolygons];

            if (f.numberofpolygons > 0)
            {
                for (int j = 0; j < f.numberofpolygons; j++) {
                    tetgenio::polygon *ply = &f.polygonlist[j];
                    tetgenio::init(ply);
                    mIO.read((char*)(&(ply->numberofvertices)), sizeof(int));
                    ply->vertexlist = new int[ply->numberofvertices];
                    mIO.read((char*)(ply->vertexlist), ply->numberofvertices*sizeof(int));
                }
            }
        }
    }

    if (meshData.numberoftetrahedra > 0)
    {
        meshData.tetrahedronlist = new int[meshData.numberoftetrahedra * 4];
        mIO.read((char*)(meshData.tetrahedronlist), sizeof(int)*meshData.numberoftetrahedra * 4);

        int numNeighbour;
        mIO.read((char*)(&numNeighbour), sizeof(int));
        if (numNeighbour > 0) {
            meshData.neighborlist = new int[meshData.numberoftetrahedra * 4];
            mIO.read((char*)(meshData.neighborlist), sizeof(int)*meshData.numberoftetrahedra * 4);
        }
    }

    return true;
}

inline bool tetgeniobyfile(tetgenio& meshData, const std::string& fn)
{
    std::ifstream mIO(fn.c_str(), std::ios_base::in | std::ios_base::binary);
    if (!mIO) {
        return false;
    }

    meshData.initialize();

    mIO.read((char*)(&meshData.numberoftetrahedra), sizeof(int));
    mIO.read((char*)(&meshData.numberoffacets), sizeof(int));
    mIO.read((char*)(&meshData.numberofpoints), sizeof(int));

    if (meshData.numberofpoints > 0) {

        meshData.pointlist = new REAL[meshData.numberofpoints * 3];
        mIO.read((char*)(meshData.pointlist), sizeof(REAL)*meshData.numberofpoints * 3);
    }

    if (meshData.numberoffacets > 0)
    {
        meshData.facetlist = new tetgenio::facet[meshData.numberoffacets];
        meshData.facetmarkerlist = new int[meshData.numberoffacets];


        mIO.read((char*)(meshData.facetmarkerlist), sizeof(int)*meshData.numberoffacets);
        for (int i = 0; i < meshData.numberoffacets; i++)
        {
            tetgenio::facet& f = meshData.facetlist[i];
            tetgenio::init(&f);

            mIO.read((char*)(&f.numberofholes), sizeof(int));
            mIO.read((char*)(&f.numberofpolygons), sizeof(int));
            if (f.numberofholes > 0) {
                f.holelist = new REAL[f.numberofholes * 3];
                mIO.read((char*)(f.holelist), sizeof(REAL)*f.numberofholes * 3);
            }

            f.polygonlist = new tetgenio::polygon[f.numberofpolygons];

            if (f.numberofpolygons > 0)
            {
                for (int j = 0; j < f.numberofpolygons; j++) {
                    tetgenio::polygon *ply = &f.polygonlist[j];
                    tetgenio::init(ply);
                    mIO.read((char*)(&(ply->numberofvertices)), sizeof(int));
                    ply->vertexlist = new int[ply->numberofvertices];
                    mIO.read((char*)(ply->vertexlist), ply->numberofvertices * sizeof(int));
                }
            }
        }
    }

    if (meshData.numberoftetrahedra > 0)
    {
        meshData.tetrahedronlist = new int[meshData.numberoftetrahedra * 4];
        mIO.read((char*)(meshData.tetrahedronlist), sizeof(int)*meshData.numberoftetrahedra * 4);

        int numNeighbour;
        mIO.read((char*)(&numNeighbour), sizeof(int));
        if (numNeighbour > 0) {
            meshData.neighborlist = new int[meshData.numberoftetrahedra * 4];
            mIO.read((char*)(meshData.neighborlist), sizeof(int)*meshData.numberoftetrahedra * 4);
        }
    }

    return true;
}
